// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// Package embed提供对正在运行的Go程序中嵌入的文件的访问。
// 
// Go导入“嵌入”的源文件可以使用
// 初始化字符串、[]字节或FS类型的变量，其中包含编译时从包目录或子目录读取的
// 文件的内容。
// 
// 例如，这里有三种方法可以嵌入名为hello的文件。txt 
// 然后在运行时打印其内容。
// 
// 将一个文件嵌入一个字符串：
// 
// 导入u“嵌入”
// 
// /go:embed hello。txt 
// var s string 
// print（s）
// 
// 将一个文件嵌入字节片：
// 
// 导入“嵌入”
// 
// /go:embedded hello。txt 
// var b[]byte 
// print（string（b））
// 
// 将一个或多个文件嵌入文件系统：
// 
// 导入“嵌入”
// 
// go:Embedded hello。txt 
// var f embed。FS 
// data，:=f.ReadFile（“hello.txt”）
// print（string（data））
// 
// Directives 
// 
// A
// 。匹配模式。
// 
// 指令必须紧跟在包含单个变量声明的行之前。
// 指令和声明之间只允许使用空行和“
// 
// 变量的类型必须是字符串类型或字节类型的切片、
// 或FS（或FS的别名）。
// 
// 例如：
// 
// 包服务器
// 
// 导入“嵌入”
// 
// /内容保存我们的静态web服务器内容。
// /go:embed image/*template/*
// /go:embed html/index。html 
// var内容嵌入。FS 
// 
// Go构建系统将识别指令，并安排使用文件系统中的匹配文件填充声明的变量
// （在上面的示例中，内容）。
// 
// 
// 简洁性接受多个空格分隔的模式，但它也可以重复，以避免在有
// 许多模式时出现很长的行。这些模式是相对于包含源文件的包目录
// 进行解释的。即使在
// Windows系统上，路径分隔符也是正斜杠。模式不能包含“.”或“…”或空路径元素，
// 也不能以斜杠开头或结尾。要匹配当前
// 目录中的所有内容，请使用“*”而不是“.”。为了允许在
// 中使用空格命名文件，模式可以写成Go双引号或反引号
// 字符串文字。
// 
// 如果一个模式命名一个目录，则该目录下的子树中的所有文件都是嵌入的（递归），除了名称以“.”开头的文件或“u”
// 不包括在内。因此，上述示例中的变量几乎等同于：
// 
// /content是我们的静态web服务器内容。wen jian defg
// var内容嵌入。FS 
// 
// 区别在于'image/*'嵌入'image/。“tempfile”而“image”则不是。
// 都不嵌入'image/dir/。临时文件'。
// 
// 如果模式以前缀“all:”开头，则遍历目录的规则将更改为包含以“.”开头的文件或者“uz”。例如，'all:image'嵌入了
// /both'image/。tempfile'和'image/dir/。临时文件'。
// 
// go:embed指令可用于导出和未导出的变量，
// 这取决于包是否希望将数据提供给其他包。
// 只能与包范围内的变量一起使用，不能与局部变量一起使用。
// 
// 模式不能与包模块外的文件匹配，例如'。git/*'或符号链接。
// 将忽略空目录的匹配项。之后，一个
// 中的每个模式必须至少匹配一个文件或非空目录。
// 
// 如果任何模式无效或匹配无效，构建将失败。
// 
// 字符串和字节
// 
// 字符串或[]字节类型变量的
// 并且该模式只能匹配一个文件。字符串或[]字节用
// 该文件的内容初始化。
// 
// 
// 在不引用嵌入的源文件中。FS，使用空白导入（导入u“嵌入”）。
// 
// 文件系统
// 
// 对于嵌入单个文件，字符串或[]字节类型的变量通常是最好的。
// FS类型允许嵌入文件树，例如静态目录
// web服务器内容，如上例所示。
// 
// FS实现了io/FS包的FS接口，因此它可以与
// 理解文件系统的任何包一起使用，包括net/http、text/template和html/template。
// 
// 例如，给定上述示例中的内容变量，我们可以编写：
// 
// http。句柄（“/static/”，http.StripPrefix（/static/”，http.FileServer（http.FS（content）））
// 
// template。ParseFS（content，*.tmpl）
// 
// Tools 
// 
// 为了支持分析Go包的工具，在
// 中找到的模式可以在“Go list”输出中找到。请参阅“go help list”输出中的EmbeddePatterns、TestEmbedPatterns、
// 和XTestEmbedPatterns字段。
// 
package embed

import (
	"errors"
	"io"
	"io/fs"
	"time"
)

// FS是文件的只读集合，通常使用
// 如果在声明时没有
// 
// 一个FS是一个只读值，因此从多个Goroutine同时使用
// 是安全的，并且可以安全地将FS类型的值分配给彼此。
// 
// FS实现FS。FS，因此它可以与任何理解
// 文件系统接口的包一起使用，包括net/http、text/template和html/template。
// 
// 有关初始化FS的更多详细信息，请参阅软件包文档。
type FS struct {
	// 编译器知道这个结构的布局。
	// 参见cmd/compile/internal/staticdata的WriteEmbed。wen jian defg
	// 相反，每个文件的名称采用“dir/elem”或“dir/elem/”的形式。
	// 可选的尾部斜杠表示文件本身就是一个目录。
	// 文件列表首先按目录排序（如果缺少目录，则将其视为“.”）
	// 然后按基数，因此，这个文件列表是：
	// 
	// p 
	// q/
	// q/r 
	// q/s/
	// q/s/t 
	// q/s/u 
	// q/v 
	// w 
	// q/#dir=。elem=q 
	// w/#dir=。elem=w 
	// q/r#dir=q elem=r 
	// q/s/#dir=q elem=s 
	// q/v#dir=q elem=v 
	// q/s/t#dir=q/s elem=t 
	// q/s/u#dir=q/s elem=u=u 
	// 相关的条目序列。
	files *[]file
}

// split将名称拆分为dir和elem，如上面FS结构中的
// 注释所述。isDir报告
// 最后的尾随斜杠是否存在，表明名称是一个目录。
func split(name string) (dir, elem string, isDir bool) {
	if name[len(name)-1] == '/' {
		isDir = true
		name = name[:len(name)-1]
	}
	i := len(name) - 1
	for i >= 0 && name[i] != '/' {
		i--
	}
	if i < 0 {
		return ".", name, isDir
	}
	return name[:i], name[i+1:], isDir
}

// trimsslash从名称中删除一个尾随斜杠，如果存在，
// 返回可能缩短的名称。
func trimSlash(name string) string {
	if len(name) > 0 && name[len(name)-1] == '/' {
		return name[:len(name)-1]
	}
	return name
}

var (
	_ fs.ReadDirFS  = FS{}
	_ fs.ReadFileFS = FS{}
)

// 文件是FS中的单个文件。
// 它实现了fs。FileInfo和fs。DirEntry。
type file struct {
	// 编译器知道这个结构的布局。
	// 参见cmd/compile/internal/staticdata的WriteEmbed。
	name string
	data string
	hash [16]byte // 截断的SHA256哈希
}

var (
	_ fs.FileInfo = (*file)(nil)
	_ fs.DirEntry = (*file)(nil)
)

func (f *file) Name() string               { _, elem, _ := split(f.name); return elem }
func (f *file) Size() int64                { return int64(len(f.data)) }
func (f *file) ModTime() time.Time         { return time.Time{} }
func (f *file) IsDir() bool                { _, _, isDir := split(f.name); return isDir }
func (f *file) Sys() any                   { return nil }
func (f *file) Type() fs.FileMode          { return f.Mode().Type() }
func (f *file) Info() (fs.FileInfo, error) { return f, nil }

func (f *file) Mode() fs.FileMode {
	if f.IsDir() {
		return fs.ModeDir | 0555
	}
	return 0444
}

// dotFile是根目录的文件，
// 在FS中的文件列表中被省略。
var dotFile = &file{name: "./"}

// lookup返回指定的文件，如果不存在，则返回nil。
func (f FS) lookup(name string) *file {
	if !fs.ValidPath(name) {
		// 编译器永远不应该发出一个名称无效的文件，
		// 所以这个检查不是严格必要的（如果名称无效，
		// 我们不应该在下面找到匹配项），但它还是一个很好的后盾。
		return nil
	}
	if name == "." {
		return dotFile
	}
	if f.files == nil {
		return nil
	}

	// 二进制搜索以查找名称在列表中的位置，
	// 然后检查名称是否位于该位置。
	dir, elem, _ := split(name)
	files := *f.files
	i := sortSearch(len(files), func(i int) bool {
		idir, ielem, _ := split(files[i].name)
		return idir > dir || idir == dir && ielem >= elem
	})
	if i < len(files) && trimSlash(files[i].name) == name {
		return &files[i]
	}
	return nil
}

// readDir返回目录dir对应的文件列表。
func (f FS) readDir(dir string) []file {
	if f.files == nil {
		return nil
	}
	// 二进制搜索以查找目录在列表中的起始和结束位置
	// 然后返回列表的该部分。
	files := *f.files
	i := sortSearch(len(files), func(i int) bool {
		idir, _, _ := split(files[i].name)
		return idir >= dir
	})
	j := sortSearch(len(files), func(j int) bool {
		jdir, _, _ := split(files[j].name)
		return jdir > dir
	})
	return files[i:j]
}

// Open打开命名文件进行读取，并将其作为fs返回。文件
// 
// 返回的文件实现io。当文件不是目录时，搜索器。
func (f FS) Open(name string) (fs.File, error) {
	file := f.lookup(name)
	if file == nil {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
	}
	if file.IsDir() {
		return &openDir{file, f.readDir(name), 0}, nil
	}
	return &openFile{file, 0}, nil
}

// ReadDir读取并返回整个命名目录。
func (f FS) ReadDir(name string) ([]fs.DirEntry, error) {
	file, err := f.Open(name)
	if err != nil {
		return nil, err
	}
	dir, ok := file.(*openDir)
	if !ok {
		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("not a directory")}
	}
	list := make([]fs.DirEntry, len(dir.files))
	for i := range list {
		list[i] = &dir.files[i]
	}
	return list, nil
}

// ReadFile读取并返回指定文件的内容。
func (f FS) ReadFile(name string) ([]byte, error) {
	file, err := f.Open(name)
	if err != nil {
		return nil, err
	}
	ofile, ok := file.(*openFile)
	if !ok {
		return nil, &fs.PathError{Op: "read", Path: name, Err: errors.New("is a directory")}
	}
	return []byte(ofile.f.data), nil
}

// 打开的文件是一个常规文件，可以打开阅读。
type openFile struct {
	f      *file // 文件本身
	offset int64 // 当前读取偏移量
}

var (
	_ io.Seeker = (*openFile)(nil)
)

func (f *openFile) Close() error               { return nil }
func (f *openFile) Stat() (fs.FileInfo, error) { return f.f, nil }

func (f *openFile) Read(b []byte) (int, error) {
	if f.offset >= int64(len(f.f.data)) {
		return 0, io.EOF
	}
	if f.offset < 0 {
		return 0, &fs.PathError{Op: "read", Path: f.f.name, Err: fs.ErrInvalid}
	}
	n := copy(b, f.f.data[f.offset:])
	f.offset += int64(n)
	return n, nil
}

func (f *openFile) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case 0:
		// 偏移量+=0 
	case 1:
		offset += f.offset
	case 2:
		offset += int64(len(f.f.data))
	}
	if offset < 0 || offset > int64(len(f.f.data)) {
		return 0, &fs.PathError{Op: "seek", Path: f.f.name, Err: fs.ErrInvalid}
	}
	f.offset = offset
	return offset, nil
}

// openDir是一个打开读取的目录。
type openDir struct {
	f      *file  // 目录文件本身
	files  []file // 目录内容
	offset int    // 读取偏移量，文件切片的索引
}

func (d *openDir) Close() error               { return nil }
func (d *openDir) Stat() (fs.FileInfo, error) { return d.f, nil }

func (d *openDir) Read([]byte) (int, error) {
	return 0, &fs.PathError{Op: "read", Path: d.f.name, Err: errors.New("is a directory")}
}

func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
	n := len(d.files) - d.offset
	if n == 0 {
		if count <= 0 {
			return nil, nil
		}
		return nil, io.EOF
	}
	if count > 0 && n > count {
		n = count
	}
	list := make([]fs.DirEntry, n)
	for i := range list {
		list[i] = &d.files[d.offset+i]
	}
	d.offset += n
	return list, nil
}

// 排序搜索类似于排序。搜索，避免导入。
func sortSearch(n int, f func(int) bool) int {
	// 定义f（-1）=false和f（n）=true。
	// 不变量：f（i-1）=假，f（j）=真。
	i, j := 0, n
	for i < j {
		h := int(uint(i+j) >> 1) // 计算h时避免溢出
		// i≤ h<j 
		if !f(h) {
			i = h + 1 // 保留f（i-1）=假
		} else {
			j = h // 保留f（j）=真
		}
	}
	// i==j，f（i-1）=假，f（j）（=f（i））==真=>答案是i
	return i
}
